/**
 *
 *      ___                       ___
 *     /\  \          ___        /\  \
 *    /::\  \        /\  \      /::\  \
 *   /:/\:\  \       \:\  \    /:/\ \  \
 *  /:/  \:\  \      /::\__\  _\:\~\ \  \
 * /:/__/ \:\__\  __/:/\/__/ /\ \:\ \ \__\
 * \:\  \  \/__/ /\/:/  /    \:\ \:\ \/__/
 *  \:\  \       \::/__/      \:\ \:\__\
 *   \:\  \       \:\__\       \:\/:/  /
 *    \:\__\       \/__/        \::/  /
 *     \/__/                     \/__/
 *
 * CIS - Clang Indexing Service is a Project Manager & Source code indexing service
 * created so as to simplify the indexing of C/C++ based source code which use
 * standardized build mechanisms.
 *
 **/

#include "../includes/compilationdb.hxx"
#include "../includes/project.hxx"
#include "../includes/cisexception.hxx"

#include <string>
#include <iostream>
#include <fstream>
#include <iterator>

#include <Poco/Logger.h>
#include <Poco/LogStream.h>
#include <Poco/File.h>
#include <Poco/Exception.h>
#include <Poco/Util/Application.h>
#include <Poco/Process.h>
#include <Poco/JSON/Object.h>
#include <Poco/JSON/Parser.h>
#include <Poco/JSON/Query.h>
#include <Poco/JSON/JSONException.h>
#include <Poco/JSON/Stringifier.h>
#include <Poco/JSON/DefaultHandler.h>
#include <Poco/JSON/Template.h>
#include <Poco/Pipe.h>
#include <Poco/StreamCopier.h>
#include <Poco/PipeStream.h>

using namespace cis;
using namespace std;

using namespace Poco;
using namespace Poco::Util;
using namespace Poco::JSON;
using namespace Poco::Dynamic;

namespace {
Poco::Logger& logger = Poco::Logger::get("CompilationDB");
Poco::LogStream logstream(logger);

static const std::string compilationdb = "compile_commands.json";
}

void CompilationDB::build()
{
    Poco::File file(proj.makefile);
    if (!file.exists()) {
        throw Poco::Exception("makefile - " + proj.makefile + ", doesn't exist!");
    }
    if (buildThread.isRunning()) {
        throw cis::BuildInProgressException();
    } else {
        logstream << "Creating build thread for project " << proj.name << std::endl;
        buildThread.start(runnable);
    }
}

void CompilationDB::buildOnThread()
{
    std::string makeReplacement = Application::instance().config().getString("build.makereplacement");
    std::vector<std::string> params = getBuildParams();

    logstream << "launching " << makeReplacement << " with params - ";
    std::copy(params.begin(), params.end(), ostream_iterator<std::string>(logstream, " "));
    logstream << std::endl;

    Poco::Pipe outPipe;
    ProcessHandle ph = Poco::Process::launch(makeReplacement, params, 0, &outPipe, &outPipe);
    Poco::PipeInputStream istr(outPipe);
    istr.rdbuf()->pubsetbuf(0, 0);
    Poco::StreamCopier::copyStreamUnbuffered(istr, proj.projectStream);
    int retcode = ph.wait();
    proj.projectStream.flush();
    logstream << "completed build for project [" << proj.name << "] with return code - " << retcode << std::endl;

    proj.projectStream << "CIS - Finished Building Project!" << std::endl;

    load();
}

void CompilationDB::load()
{
    Poco::File f(cdb);
    if (!f.exists())
        return;
    else {
        DefaultHandler handler;
        Var result;
        Poco::JSON::Parser parser;
        parser.setHandler(&handler);
        std::fstream fstr(cdb.c_str());

        parser.parse(fstr);
        result = handler.result();
        Array::Ptr arrayFiles = result.extract<Array::Ptr>();
        size_t sz = arrayFiles->size();
        {
            Poco::ScopedLock<Poco::Mutex> lck(mtx);
            for (size_t i = 0; i < sz; i++) {
                Object::Ptr object = arrayFiles->getObject(i);
                try {
                    if (object->has("directory") && object->has("filename") && object->has("command")) {
                        std::string filename = object->get("directory").convert<std::string>() + Poco::Path::separator() + object->get("filename").convert<std::string>();
                        std::string command = object->get("command").convert<std::string>();
                        fileFlagsMap[filename] = command;
                    }
                } catch(...) {
                    // do nothing..
                }
            }
        }
    }
}

std::vector<std::string> CompilationDB::getBuildParams()
{
    std::vector<std::string> params;
    Poco::Path mf(proj.makefile);
    mf = mf.parent();

    params.push_back("--cis-docompile");
    params.push_back("--cis-makecommand");
    params.push_back("make");
    params.push_back("--cis-compiledb");
    params.push_back(cdb);
    params.push_back("--cis-makeparams");
    params.push_back(proj.makeparams);
    return params;
}

std::string CompilationDB::getFileFlags(const std::string& filename)
{
    Poco::ScopedLock<Poco::Mutex> lck(mtx);
    FileFlagsMap::Iterator it = fileFlagsMap.find(filename);
    if (it != fileFlagsMap.end())
        return it->second;
    return "";
}

CompilationDB::CompilationDB(const Project& proj) : proj(proj), runnable(*this, &CompilationDB::buildOnThread)
{
    cdb = proj.buildpath + "/" + compilationdb;
}